/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using EnvDTE;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	public class VSProjectItem : ProjectItemBase
	{
		#region Public Properties
		public override bool IsVisible
		{
			get
			{
				return GetProjectItem(false, null) != null;
			}
		}

		public override IProjectItem DependentUpon
		{
			get
			{
				if (IsDirectory || IsExternal)
					return null;

				ProjectItem item = GetProjectItem(false, null);

				if (item == null)
					return null;

				ProjectItems items = item.Collection;

				if (items == null)
					return null;

				ProjectItem parent = items.Parent as ProjectItem;

				if (parent == null)
					return null;

				return new VSProjectItem(project, BaseLocation, parent.get_FileNames(1));
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (IsDirectory)
					throw new InvalidOperationException(Resources.Errors.ProjectItemNoDependencyForDirectory);
				else if (IsExternal)
					throw new InvalidOperationException(Resources.Errors.ProjectItemExternalDepedencyNotSupported);

				VSProjectItem vsItem = value as VSProjectItem;

				if (vsItem == null)
					throw new ArgumentException(Resources.Errors.ProjectItemVSDependencyRequired, "value");
				else if (vsItem.IsExternal)
					throw new InvalidOperationException(Resources.Errors.ProjectItemExternalDepedencyNotSupported);

				SystemPath directory = SystemPath.DirectoryOf(Path, false);
				SystemPath dependentDirectory = SystemPath.DirectoryOf(value.Path, false);

				if (!directory.Equals(dependentDirectory))
					throw new ArgumentException(Resources.Errors.ProjectItemDependencyDifferentDirectory, "value");

				// create the item with a dependency or create the association if the itemm already exists
				GetProjectItem(true, vsItem);
			}
		}
		#endregion

		#region Private / Protected
		private readonly Project project;
		#endregion

		#region Constructors
		public VSProjectItem(object projectBase, Uri baseLocation, string path)
			: this((Project) projectBase, baseLocation, path, false)
		{
		}

		public VSProjectItem(object projectBase, Uri baseLocation, string path, bool forceDirectory)
			: this((Project) projectBase, baseLocation, path, forceDirectory)
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="VSProjectItem" /> class.
		/// </summary>
		internal VSProjectItem(Project project, Uri baseLocation, string path, bool forceDirectory)
			: base(baseLocation, path, forceDirectory)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			this.project = project;
		}
		#endregion

		#region Methods
		public override IProjectItem MakeDependencyFor(string path)
		{
			VSProjectItem item = new VSProjectItem(project, BaseLocation, path);

			// this method will create the dependency
			item.GetProjectItem(true, this);

			return item;
		}

		[System.Diagnostics.DebuggerHidden]
		public override void Checkout()
		{
			string path = Path;

			if (project.DTE.SourceControl.IsItemUnderSCC(path))
			{
				if (!project.DTE.SourceControl.CheckOutItem(path))
					throw new System.ComponentModel.Design.CheckoutException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Resources.Errors.CheckoutException, path));
			}
			else
			{
				try
				{
					IsReadOnly = false;
				}
				catch (FileNotFoundException) { }
				catch (DirectoryNotFoundException) { }
			}
		}

		[System.Diagnostics.DebuggerHidden]
		public override bool Hide()
		{
			try
			{
				if (IsExternal)
					project.ProjectItems.Item(RelativePathWithoutDirectorySeparator).Remove();		// the property returns the full path if IsExternal is true
				else
				{
					string relativePath = RelativePath;

					string[] parts = relativePath.Split(new char[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

					ProjectItem item = null;

					for (int i = 0; i < parts.Length; i++)
					{
						string part = parts[i];

						// NOTE: There doesn't seem to be any way to check whether the item is included other than catching ArgumentException
						if (item == null)
							item = project.ProjectItems.Item(part);
						else
							item = item.ProjectItems.Item(part);
					}

					if (item == null)
						return false;
					else
						item.Remove();
				}

				return true;
			}
			catch (ArgumentException)
			{
				return false;
			}
		}

		public override void Show()
		{
			EnsureExists();

			// get the project item, creating all invalid parts
			GetProjectItem(true, null);
		}

		public override void Open()
		{
			ProjectItem item = GetProjectItem(false, null);

			if (item == null)
			{
				if (IsDirectory)
					throw new DirectoryNotFoundException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Resources.Errors.DirectoryNotFound, Path));
				else
					throw new FileNotFoundException(Resources.Errors.FileNotFound, Path);
			}
			else
				item.Open(EnvDTE.Constants.vsViewKindPrimary).Visible = true;
		}

		public override void Delete()
		{
			Checkout();

			ProjectItem item = GetProjectItem(false, null);

			if (item == null)
			{
				if (Exists)
				{
					try
					{
						if (IsDirectory)
							Directory.Delete(Path, true);
						else
							File.Delete(Path);
					}
					catch (DirectoryNotFoundException) { }
					catch (FileNotFoundException) { }
				}
			}
			else
				item.Delete();
		}

		[System.Diagnostics.DebuggerHidden]
		private ProjectItem GetProjectItem(bool createInvalidParts, VSProjectItem dependencyItem)
		{
			ProjectItem item = null;

			if (IsExternal)
			// absolute path
			{
				string path = Path;

				// NOTE: There doesn't seem to be any way to check whether the item is included other than catching ArgumentException
				try
				{
					item = project.ProjectItems.Item(path);
				}
				catch (ArgumentException)
				{
					if (!createInvalidParts)
						return null;

					if (IsDirectory)
						item = project.ProjectItems.AddFromDirectory(path);
					else
						item = project.ProjectItems.AddFromFile(path);
				}
			}
			else if (dependencyItem != null)
			{
				ProjectItems items = dependencyItem.GetProjectItem(true, null).ProjectItems;
				string path = Path;
				
				// NOTE: There doesn't seem to be any way to check whether the item is included other than catching ArgumentException
				try
				{
					item = items.Item(path);
				}
				catch (ArgumentException)
				{
					if (createInvalidParts)
					{
						try
						{
							if (File.Exists(path))
								item = items.AddFromFile(path);
						}
						// occurred in testing when the file doesn't exist
						catch (System.Runtime.InteropServices.COMException) { }
					}
				}
			}
			else
			// relative path
			{
				string path = BasePath;
				string relativePath = RelativePath;

				string[] parts = relativePath.Split(new char[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

				for (int i = 0; i < parts.Length; i++)
				{
					string part = parts[i];

					path = System.IO.Path.Combine(path, part);

					// NOTE: There doesn't seem to be any way to check whether the item is included other than catching ArgumentException
					try
					{
						if (item == null)
							item = project.ProjectItems.Item(part);
						else
							item = item.ProjectItems.Item(part);
					}
					catch (ArgumentException)
					{
						if (!createInvalidParts)
							return null;

						if (i < parts.Length - 1 || IsDirectory)
						{
							if (item == null)
								item = project.ProjectItems.AddFromDirectory(path);
							else
								item = item.ProjectItems.AddFromDirectory(path);
						}
						else
						// create file (this code is only executed on the final iteration)
						{
							if (item == null)
								item = project.ProjectItems.AddFromFile(path);
							else
								item = item.ProjectItems.AddFromFile(path);
						}
					}
				}
			}

			return item;
		}
		#endregion
	}
}
